/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package de.unioninvestment.eai.portal.portlet.crud.scripting.domain.container.rest;
import static java.util.Collections.emptyList;
import groovy.lang.Closure;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import org.apache.commons.lang.LocaleUtils;
import org.apache.http.HttpResponse;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.unioninvestment.eai.portal.portlet.crud.config.GroovyScript;
import de.unioninvestment.eai.portal.portlet.crud.config.ReSTAttributeConfig;
import de.unioninvestment.eai.portal.portlet.crud.config.ReSTContainerConfig;
import de.unioninvestment.eai.portal.portlet.crud.domain.exception.InvalidConfigurationException;
import de.unioninvestment.eai.portal.support.scripting.ScriptBuilder;
public abstract class AbstractParser implements PayloadParser {
private static final Logger LOGGER = LoggerFactory
.getLogger(AbstractParser.class);
private ReSTContainerConfig config;
private ScriptBuilder scriptBuilder;
private ValueConverter converter = new ValueConverter();
/**
* To be implemented by subclasses. Should parse the ReST content and return
* a Java Model. (Results of JSONSlurper or XMLSlurper).
*
* @param reader
* provides the body content
* @return the model
* @throws IOException
* propagated from subroutines
*/
protected abstract Object parseData(Reader reader) throws IOException;
/**
* @param config
* the ReST Configuration
* @param scriptBuilder
* needed for Closure instantation
*/
public AbstractParser(ReSTContainerConfig config,
ScriptBuilder scriptBuilder) {
this.config = config;
this.scriptBuilder = scriptBuilder;
}
@Override
public List<Object[]> getRows(HttpResponse response)
throws IOException {
Reader reader = getReader(response);
Object parsedData = parseData(reader);
Iterable<?> collection = getCollection(parsedData);
List<ReSTAttributeConfig> attributes = config
.getQuery().getAttribute();
Closure<?>[] attributeClosures = new Closure<?>[attributes.size()];
Locale[] locales = new Locale[attributes.size()];
Class<?>[] types = new Class<?>[attributes.size()];
int i = 0;
for (ReSTAttributeConfig attr : attributes) {
attributeClosures[i] = scriptBuilder.buildClosure(attr.getPath());
locales[i] = effectiveLocale(response, attr);
types[i] = attr.getType();
i++;
}
List<Object[]> result = new LinkedList<Object[]>();
int counter = 0;
int notNullCounter = 0;
for (Object entry : (Iterable<?>) collection) {
if (entry != null) {
Object[] item = new Object[attributeClosures.length];
for (int j = 0; j < item.length; j++) {
ReSTAttributeConfig attr = attributes.get(j);
Object valueFromClosure = callClosureAgainstDelegate(
attributeClosures[j], entry);
Object unmarshaledValue = unmarshalValue(valueFromClosure);
Object convertedValue = converter.convertValue(types[j],
attr.getFormat(), locales[j], unmarshaledValue);
item[j] = convertedValue;
}
result.add(item);
notNullCounter++;
}
counter++;
}
if (notNullCounter == 0 && counter > 0) {
LOGGER.warn("ReST Collection only contained NULL elements. The collection may be wrong");
}
return result;
}
/**
* Parser specific conversion/unmarshaling
*
* @param valueReturnedByClosure
* @return the unmarshaled value (e.g. a simple Java type)
*/
protected Object unmarshalValue(Object valueReturnedByClosure) {
return valueReturnedByClosure;
}
/**
* Provide a collection from the parsed data, probably by evaluating the
* collection attribute from the configuration.
*
* @param parsedData
* the parsed ReST response data
* @return something iterable that maps to table rows
*/
protected Iterable<?> getCollection(Object parsedData) {
Object collection = parsedData;
GroovyScript collectionScript = config.getQuery()
.getCollection();
if (collectionScript != null && collectionScript.getSource() != null) {
Closure<?> closure = scriptBuilder.buildClosure(collectionScript);
try {
collection = callClosureAgainstDelegate(closure, parsedData);
} catch (NullPointerException e) {
LOGGER.warn("NPE while querying for ReST collection. Assuming empty collection.");
return emptyList();
}
}
if (collection instanceof Iterable) {
return (Iterable<?>) collection;
} else if (collection == null) {
LOGGER.info("ReST Collection is null. Assuming empty collection");
return emptyList();
} else {
throw new InvalidConfigurationException(
"portlet.crud.error.config.rest.wrongCollectionType",
collection.getClass().getName());
}
}
/**
* @param response
* the ReST Response
* @return a reader providing the HTTP payload and respecting the response
* encoding
* @throws IOException
*/
protected Reader getReader(HttpResponse response) throws IOException {
ContentType contentType = ContentType.getOrDefault(response
.getEntity());
InputStream stream = response.getEntity().getContent();
InputStreamReader reader = new InputStreamReader(stream,
getResponseCharset(contentType));
return reader;
}
private Charset getResponseCharset(ContentType contentType) {
Charset charset = contentType.getCharset();
if (charset == null) {
charset = Charset.forName(config.getCharset());
}
return charset;
}
private Locale effectiveLocale(HttpResponse response,
ReSTAttributeConfig attr) {
Locale locale = response.getLocale();
if (attr.getLocale() != null) {
locale = LocaleUtils.toLocale(attr.getLocale());
}
return locale;
}
/**
* Utility method that configures the given closure to delegate everything
* to a given object
*
* @param closure
* the closure to configure and call
* @param delegate
* the delegate object
* @return the result of the closure
*/
protected Object callClosureAgainstDelegate(Closure<?> closure,
Object delegate) {
closure.setDelegate(delegate);
closure.setResolveStrategy(Closure.DELEGATE_ONLY);
return closure.call(delegate);
}
/**
* @param converter
* for Testing
*/
void setConverter(ValueConverter converter) {
this.converter = converter;
}
}